/*
 * Middleware
 * Copyright (C) 2010/11
 * Riccardo Pulvirenti, Giuseppe Ravidà, Andrea Tino
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANDABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses
 */

/*!
 * File: core.cpp
 */

#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <exception>

#include "core.hpp"
#include "ui.hpp"
#include "manager.hpp"
#include "worker_discovery.hpp"
#include "worker.hpp"
#include "worker_descriptor.hpp"
#include "address.hpp"

using namespace middleware;

//-----------------------------------------------------------------------------
// Constructors and destructor
/* Default constructor */
CORE::CORE() {
	this->_config_filename = kConfigFileName;
}
/* Constructor specifying file name */
CORE::CORE(const std::string& filename) {
	this->_config_filename = filename;
}
/* Destructor */
CORE::~CORE() {

}
//-----------------------------------------------------------------------------
// Public members
/* Execution for core (NOT A THREAD) */
void CORE::exec() {
	std::string smode; int mode = -1;
	std::cout << kCOREEtq << "CORE now started!" << std::endl;
	try {
		// Bonjour
		this->bonjour();
		// First grab configuration, depending on it,
		smode = (*this)[kCPMode];
		std::cout << kCOREEtq << "Mode is: " << smode << std::endl;
		mode = this->GetMode(smode);
		switch (mode) {
			case 1:
			{
				// Act as UI
				UIConfig uiconf;
				std::cout << kCOREEtq << "Machine as UI" << std::endl;
				uiconf.ui_ip_toman = ((*this)[kCPAddrToManIp]);
				uiconf.ui_port_toman = ((*this)[kCPAddrToManPort]);
				uiconf.ui_ip_tow_bind = ((*this)[kCPAddrToBindIp]);
				uiconf.ui_port_tow_bind = ((*this)[kCPAddrToBindPort]);
				std::cout << kCOREEtq << "UI configuration: tomanIP=" <<
						uiconf.ui_ip_toman << " tomanPORT=" <<
						uiconf.ui_port_toman << " bind_towPORT=" <<
						uiconf.ui_port_tow_bind << std::endl;
				std::cout << kCOREEtq << "Creating UI..." << std::endl;
				UI ui(uiconf);
				ui.run();
				std::cout << kCOREEtq << "UI created and launched!" <<
						std::endl;
				std::cout << kCOREEtq << "Joined on UI..." << std::endl;
				ui.join(); /* Wait for UI to finish */
				break;
			}
			case 2:
			{
				// Act as Manager
				ManagerConfig manconf;
				std::cout << kCOREEtq << "Machine as MANAGER" << std::endl;
				manconf.man_ip_todisc = ((*this)[kCPAddrToDiscIp]);
				manconf.man_port_todisc = ((*this)[kCPAddrToDiscPort]);
				manconf.man_ip_toui_bind = ((*this)[kCPAddrToBindIp]);
				manconf.man_port_toui_bind = ((*this)[kCPAddrToBindPort]);
				std::cout << kCOREEtq << "MAN configuration: todiscIP=" <<
						manconf.man_ip_todisc << " todiscPORT=" <<
						manconf.man_port_todisc << " bind_touiPORT=" <<
						manconf.man_port_toui_bind << std::endl;
				std::cout << kCOREEtq << "Creating MAN..." << std::endl;
				Manager man(manconf);
				man.run();
				std::cout << kCOREEtq << "MAN created and launched!" <<
						std::endl;
				std::cout << kCOREEtq << "Joined on MAN..." << std::endl;
				man.join();
				break;
			}
			case 3:
			{
				// Act as Worker
				WorkerConfig wconf; /* For worker */
				Address addr; /* For WorkerDescriptor */
				WorkerCapabilities capab;  /* For WorkerDescriptor */
				std::cout << kCOREEtq << "Machine as WORKER" << std::endl;
				wconf.worker_ip_todisc = ((*this)[kCPAddrToDiscIp]);
				wconf.worker_port_todisc = ((*this)[kCPAddrToDiscPort]);
				addr.set_ip(boost::lexical_cast<InetIpAddr>(
						((*this)[kCPAddrToBindIp])));
				addr.set_port(boost::lexical_cast<InetPort>(
						((*this)[kCPAddrToBindPort])));
				std::cout << kCOREEtq << "WORKER configuration: todiscIP=" <<
						wconf.worker_ip_todisc << " todiscPORT=" <<
						wconf.worker_port_todisc << " bind_tomanIP=" <<
						addr.get_ip() << " bind_tomanPORT=" <<
						addr.get_port() << std::endl;
				capab.capab_bandwidth = boost::lexical_cast<double>(
						((*this)[kCPWorkerCapabB]));
				capab.capab_core_num = boost::lexical_cast<unsigned int>(
						((*this)[kCPWorkerCapabC]));
				capab.capab_cpu_mhz = boost::lexical_cast<double>(
						((*this)[kCPWorkerCapabZ]));
				capab.capab_cpu_num = boost::lexical_cast<unsigned int>(
						((*this)[kCPWorkerCapabP]));
				capab.capab_hops = boost::lexical_cast<unsigned int>(
						((*this)[kCPWorkerCapabD]));
				capab.capab_ram_mb = boost::lexical_cast<double>(
						((*this)[kCPWorkerCapabM]));
				std::cout << "Worker capabilities set: B=" <<
						capab.capab_bandwidth << " C=" <<
						capab.capab_core_num << " Z=" <<
						capab.capab_cpu_mhz << " P=" <<
						capab.capab_cpu_num << " D=" <<
						capab.capab_hops << " M=" <<
						capab.capab_ram_mb << std::endl;
				std::cout << kCOREEtq << "Creating WORKER..." << std::endl;
				WorkerDescriptor wd(addr, capab); /* For worker */
				Worker worker(wd, wconf);
				worker.run();
				std::cout << kCOREEtq << "WORKER created and launched!" <<
						std::endl;
				std::cout << kCOREEtq << "Joined on WORKER..." << std::endl;
				worker.join();
				break;
			}
			case 4:
			{
				// Act as Discovery
				DiscoveryConfig discconf;
				std::cout << kCOREEtq << "Machine as DISCOVERY" << std::endl;
				discconf.disc_ip_toman_bind = ((*this)[kCPAddrToBindIp]);
				discconf.disc_port_toman_bind = ((*this)[kCPAddrToBindPort]);
				discconf.disc_ip_toworkers_bind = ((*this)[kCPAddrToBind2Ip]);
				discconf.disc_port_toworkers_bind = ((*this)[kCPAddrToBind2Port]);
				std::cout << kCOREEtq <<
						"DISC configuration: bind_tomanPORT=" <<
						discconf.disc_port_toman_bind <<
						" bind_toworkersPORT=" <<
						discconf.disc_port_toworkers_bind << std::endl;
				std::cout << kCOREEtq << "Creating DISC..." << std::endl;
				WorkerDiscovery disc(discconf);
				disc.run();
				std::cout << kCOREEtq << "DISC created and launched!" <<
						std::endl;
				std::cout << kCOREEtq << "Joined on DISC..." << std::endl;
				disc.join();
				break;
			}
			default:
				throw CoreException("Unrecognized mode value!");
				break;
		}
	} catch(CoreException& e) {
		std::cout << kCOREEtq << "Error: " << e.what() << std::endl;
		std::cout << kCOREEtq << "Now exiting!" << std::endl;
	} catch(std::exception e) {
		std::cout << kCOREEtq << "Error: " << e.what() << std::endl;
		std::cout << kCOREEtq << "Now exiting!" << std::endl;
	}
	std::cout << kCOREEtq << "Exiting..." << std::endl;
}
/* Param indexer */
std::string CORE::operator[](const std::string& param_name)
		throw(CoreException) {
	std::string line;
	std::string par;
	std::ifstream config_file;
	int delimiter = 0;
	this->Open(config_file);
	if (config_file.is_open()) {
		while (config_file.good()) {
			getline(config_file, line); /* Getting varX:varY */
			if (line.substr(0, (delimiter =
					line.find(kConfigFileDelimiter))) == param_name) {
				// Found ==> Getting value
				this->Close(config_file);
				return line.substr((delimiter + 1), line.size() - delimiter);
			}
		}
		this->Close(config_file);
		// Not found
		return kParameterNotFound;
	} else {
		throw CoreException("Impossible to access data! Invalid configuration file.");
	}
}
//-----------------------------------------------------------------------------
// Private members
void CORE::bonjour() throw(CoreException) {
	std::cout << kCOREEtq << "Initialization sequence now started..." <<
				std::endl;
	std::cout << kCOREEtq << "Checking configuration file: " <<
			this->_config_filename << " (local path)..." << std::endl;
	try {
		std::ifstream config_file;
		this->Open(config_file);
		this->Close(config_file);
	} catch(CoreException& e) {
		std::cout << "Could not open configuration file!" <<
				std::endl;
		throw CoreException("Incorrect configuration file.");
	}
	std::cout << kCOREEtq << "Configuration file ok!" << std::endl;
	std::cout << kCOREEtq << "Initialization completed!" << std::endl;
}
/* Mode */
int CORE::GetMode(std::string& smode) {
	if (smode == kCVModeUserInterface) return 1;
	else if (smode == kCVModeManager) return 2;
	else if (smode == kCVModeWorker) return 3;
	else if (smode == kCVModeDiscovery) return 4;
	return -1;
}
/* File opener */
void CORE::Open(std::ifstream& config_file) throw(CoreException) {
	config_file.open(this->_config_filename.c_str());
	if (!(config_file.is_open()))
		throw CoreException("Unable to open configuration file!");
}
/* File closing */
void CORE::Close(std::ifstream& config_file) {
	config_file.close();
}
